const { cons, car, cdr } = require('./lang')


const list = (...x) => (
  x.length === 0
    ? null
    : cons(x[0], list(...x.slice(1)))
);

const append = (l, r) => (
  l === null
    ? r
    : cons(
        car(l),
        append(cdr(l), r)
      )
);


const reverse = (l) => (
  l === null
    ? null
    : append(
        reverse(cdr(l)),
        list(car(l))
      )
);


const list_ref = (l, n) => {
  if (l === null || n < 0) {
    throw new Error('out of index error')
  }

  return n === 0
    ? car(l)
    : list_ref(cdr(l), n - 1)
}


const length = (l) => (
  l === null
    ? 0
    : 1 + length(cdr(l))
);

const first = (l) => {
  if (l === null) {
    throw new Error('list is null')
  }
  return car(l)
};

const last = (l) =>{
  if (l === null) {
    throw new Error('list is null')
  }
  return cdr(l) === null
      ? car(l)
      : last(cdr(l))
 }


const equal = (l, r) =>
  (
    l === null
    && r === null
  ) || (
    l !== null
    && r !== null
    && car(l) === car(r)
    && equal(cdr(l), cdr(r))
  );


const map = (l, proc) => (
  l === null
    ? null
    : cons(proc(car(l)), map(cdr(l), proc))
);


// 不在乎返回值
const foreach = (l, handle) => (
  l === null
    ? null
    : cons(handle(car(l)), foreach(cdr(l), handle))
);


const filter = (l, predicate) =>(
  l === null
    ? null
    : predicate(car(l))
        ? cons(car(l), filter(cdr(l), predicate))
        : filter(cdr(l), predicate)
)


// TODO: 用 accumulate 实现 map、append 和 length
const accumulate = (l, initial, op ) => (
  l === null
    ? initial
    : accumulate(
        cdr(l),
        op(initial, car(l)),
        op
    )
);

module.exports = {
  list,
  append,
  reverse,
  list_ref,
  length,
  first,
  last,
  equal,
  map,
  foreach,
  filter,
  accumulate
}